/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.icom.maf.core;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import net.icom.maf.data.Constants;
import net.icom.maf.data.IntQueue;
import net.icom.maf.event.TimerEvent;
import net.icom.maf.event.Event;
import net.icom.maf.event.EventDispatcher;
import net.icom.maf.event.IEventDispatcher;
import net.icom.maf.event.IEventListener;
import net.icom.maf.event.KeyEvent;
import net.icom.maf.event.PaintEvent;
import net.icom.maf.logging.ILogger;

/**
 * work hog for the framework; essentially this is responsible for 'cycling' the 
 * application and communicate any interesting events to registered listeners. the reason 
 * for being a majority events driven framework is to ensure that the application 
 * remains de-coupled and thus flexible to handle ad-hoc requirements (trade-off = performance 
 * overheads). 
 * @author josh.newnham
 */
public class Engine extends GameCanvas implements Runnable, IEventDispatcher {

   //<editor-fold defaultstate="collapsed" desc="private fields">
   
    private int fillColour = Constants.COLOUR_WHITE;
    private int fps = 30; 
    private Thread applicationThread = null;
    private boolean running = true;
    private boolean paused = false; 
    private EventDispatcher eventDispatcher = null; 
    
    protected IntQueue keyPressedQ = null;
    protected IntQueue keyPressedActionQ = null;
    protected IntQueue keyReleasedQ = null;
    protected IntQueue keyReleasedActionQ = null;
    
    private int x;
    private int y;
    private int width;
    private int height;
    
   //</editor-fold>
    
    public Engine( int x, int y, int width, int height ){
        super( true ); // supress key presses
        
        setFullScreenMode(true);
        
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        
        init(); 
    }
    
    //<editor-fold defaultstate="collapsed" desc="setup">
    
    private void init(){
        
        initInputQueues();
        
        eventDispatcher = new EventDispatcher(); 
        
        applicationThread = new Thread( this );
        applicationThread.start();
    }
    
    private void initInputQueues(){
        // set up the queues to hold the key events
        keyPressedQ = new IntQueue( 10 );
        keyPressedActionQ = new IntQueue( 10 );
        keyReleasedQ = new IntQueue( 10 );
        keyReleasedActionQ = new IntQueue( 10 );
    }
    
    //</editor-fold>
    
    /**
     * this is the applications work hog - it's essentially the processor for any application; 
     * all applications will be forced to be processed in the following sequence:
     * 1. process user input - dispatch user input events out to registered listeners
     * 2. cycle application logic - animation etc will reside in here 
     * 3. process registered/active services
     * 4. render results 
     * 
     * motivation for having all of this here is to ensure that this thread is the main driver for 
     * all processing thus removing the likely hood of having two threads over-lapping in the same code
     */
    public void run(){
        long currTime = System.currentTimeMillis();
        
        while( running ){
            long elapsedTime = System.currentTimeMillis() - currTime;
            currTime += elapsedTime;
            
            if( paused ){
                synchronized( this ){
                    try{
                        this.wait();
                    } catch( Exception e ){}
                }
            }
            
            if( paused )
                continue;
            
            if( !running )
                break;
                                  
            processUserInput( elapsedTime );
            cycle( elapsedTime );   
            processServices( elapsedTime ); 
            processRendering( elapsedTime ); 
            
            paceApplicationCycle( elapsedTime );
        }
    }        
    
    //<editor-fold defaultstate="collapsed" desc="processing methods">
    
    /**
     * 
     * @param elapsedTime
     */
    private void processUserInput( long elapsedTime ){
        KeyEvent ke = null; 
        
        // key pressed
        if( keyPressedQ.peek() != Constants.NULL_INT ){
            ke = new KeyEvent( KeyEvent.KEY_STATE_DOWN );
            ke.setKeyCode( keyPressedQ.dequeue() );
            ke.setGameAction( keyPressedActionQ.dequeue() );
            ke.setDispatcher( this ); 
            
            dispatchEvent( ke );
        }
            
        // key released
        if( keyReleasedQ.peek() != Constants.NULL_INT ){
            ke = new KeyEvent( KeyEvent.KEY_STATE_UP );
            ke.setKeyCode( keyPressedQ.dequeue() );
            ke.setGameAction( keyPressedActionQ.dequeue() );
            ke.setKeyDownTime( keyReleasedTimeStamp - keyPressedTimeStamp );
            ke.setDispatcher( this ); 
            
            dispatchEvent( ke );
        }                
    }
    
    private void cycle( long elapsedTime ){
        TimerEvent ce = new TimerEvent(); 
        ce.setElapsedTime( elapsedTime );
        ce.setDispatcher( this ); 
        
        dispatchEvent( ce );
    }
    
    private void processServices( long elapsedTime ){
        
    }
    
    private void processRendering( long elapsedTime ){
        Graphics g = this.getGraphics();
        
        g.setColor( fillColour );
        g.fillRect( x, y, width, height );
        
        PaintEvent pe = new PaintEvent( g );
        pe.setDispatcher( this ); 
        pe.setElapsedTime( elapsedTime );
        dispatchEvent( pe );
                
        flushGraphics( x, y, width, height );
    }
    
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="input handling methods">
    
    protected long keyPressedTimeStamp = 0;
    protected long keyReleasedTimeStamp = 0;
    
    private boolean inputLock = false;
    
    /**
     * called from the midlets/systems thread - take the input and place it in the 
     * queue so it can be processed in the application cycle
     * @param keyCode
     */
    public void keyPressed(int keyCode) {               
        
        if( inputLock )
            return;
        
        keyPressedTimeStamp = System.currentTimeMillis();
        
        keyPressedQ.queue( keyCode );
        keyPressedActionQ.queue( getGameAction( keyCode ) );      
    }
    
    /**
     * called from the midlets/systems thread - take the input and place it in the 
     * queue so it can be processed in the application cycle
     * @param keyCode
     */
    public void keyReleased(int keyCode) {        
        keyReleasedTimeStamp = System.currentTimeMillis();
        
        keyReleasedQ.queue( keyCode );
        keyReleasedActionQ.queue( getGameAction( keyCode ) );
    }
    
    public void setInputLock( boolean inputLock ){
        this.inputLock = inputLock;
    }
    
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="thread management">   
    
    protected void exit(){
        running = false;
    }
    
    protected void pause(){
        paused = true;
    }
    
    public void resume(){
        paused = false;
        
        synchronized( this ){
            try{
                this.notify();
            } catch( Exception e ){}
        }
    }
    
    public boolean isPaused(){
        return running && paused && (applicationThread != null && applicationThread.isAlive());
    }
    
    /** method used to regulate the speed of the application **/
    private void paceApplicationCycle( long currTime ){
        // sleep if necessary to make a smooth framerate
        long endTime = System.currentTimeMillis() - currTime;
        
        if (endTime < (1000 / this.fps)) {
            try {
                // frames per second
                applicationThread.sleep((1000 / this.fps) - endTime);
            } catch (Exception e) { }
        } else {
            try {
                applicationThread.yield();
            } catch (Exception e) { }
        }
    }
    
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="logging">
    private ILogger logger = null;
    
    public void setLogger( ILogger logger ){
        this.logger = logger; 
    }
    
    public ILogger getLogger(){
        return this.logger; 
    }
    
    private void log( int level, String message ){
        if( this.logger != null )
            this.logger.log( level, message );
    }    
    
    //</editor-fold>
    
    //<editor-fold defaultstate="collapsed" desc="event dispatching">
    
    public String getName() {
        return "net.icom.maf.core.Engine";
    }

    public void addListener(String eventName, IEventListener listener) {
        eventDispatcher.addListener( eventName, listener );
    }

    public void removeListener(String eventName, IEventListener listener) {
        eventDispatcher.addListener( eventName, listener );
    }

    public void removeListener(IEventListener listener) {
        eventDispatcher.removeListener( listener );
    }

    public void dispatchEvent(Event e) {
        eventDispatcher.dispatchEvent( e );
    }
    
    //</editor-fold>
        
}
